In a previous post, I’ve outlined how to enable HTTPS traffic by installing an SSL/TLS certificate on our nginx server using Certbot and Let’s Encrypt. Since Let’s Encrypt certificates are only valid for 90 days, let’s setup automatic renewal in this post.
To follow this process, I would assume that you have - installed Certbot - obtained the SSL Certificate
What happens when certs expire
nginx loads the certificate and key into memory when it starts or reloads. After that, it doesn’t re‑read the files on disk for every connection.
If the certificate later expires on disk but nginx hasn’t been reloaded, nginx will continue to present the old (now expired) certificate to clients.
- From nginx’s perspective, it’s still a valid cert/key pair in memory. - From our app’s perspective, it will still receive HTTPS traffic because nginx will still terminate TLS with the expired cert and proxy to the app. The app itself doesn’t know or care about the certificate validity.
- The problem is entirely at the client side. From the client’s perspective, the cert is expired, so browsers and tools like curl will show “Your connection is not private” warnings or refuse the connection depending on their trust policy.
After cert renewal, it is critical to reload nginx to swap its in‑memory cert for the fresh one on disk.
How renewal works
Certbot is designed to set up automatic renewal via a systemd timer or cron job that runs twice a day to renew the certificates before they expire. This process checks all installed certificates and renews any that are within 30 days of expiration. This ensures that the certificate is always valid and our website remains secure without any intervention on our part.
However, this behavior varies based on the package manager from which we install Certbot. Thus we must confirm it’s actually installed and enabled on our system.
Check certificate expiry
Let’s first check the current status of all our certificates with sudo certbot certificates.
If any has expired, run:
sudo certbot renew
sudo systemctl reload nginxWe can then check both the issue and expiry date with
echo | openssl s_client -connect custom-domain.com:443 -servername custom-domain.com 2>/dev/null | openssl x509 -noout -datesDry run
We will first dry run the renewal process to ensure existing renewal hooks and ACME challenge process are working. This command simulates the process without actually renewing our real certs:
sudo certbot renew --dry-run
If the command completes successfully, we’ll see output indicating that the dry run succeeded for all our domains. A dry‑run uses Let’s Encrypt’s staging servers, so it doesn’t consume your real certificate quota, but it does exercise the exact same logic to verify: * nginx was reachable from the outside world on port 80 (for the HTTP‑01 challenge). * Certbot could write the temporary challenge file into /.well-known/acme-challenge/ and that nginx served it correctly. * renewal hooks (reloading nginx after renewal, if configured) can run without error.
Check if auto-renewal is setup properly
On most modern Linux distributions, the Certbot package installs a systemd timer (certbot.timer) that runs certbot renew twice daily.
On older systems, it may instead install a cron job in /etc/cron.d/certbot.
To verify if your Certbot package has these automation: - For systemd: run systemctl list-timers | grep certbot If you see certbot.timer … 2h left … certbot.service, it means systemd will run certbot renew automatically. - For cron (older setups): run sudo crontab -l | grep certbot If you see a line that runs certbot renew twice a day, you are all set.
Unfortunately, for my Oracle Linux 9 VM, both are missing. So nothing is currently scheduled to run certbot renew. The dry‑run we did earlier only confirmed that renewal would work if invoked, but right now there’s no timer or cron job actually invoking it.
Setup auto-renewal
Let’s setup renewal manually in our Oracle Linux VM. Pick either step 1A (certbot.timer) or 1B (cron job), then proceed to complete step 2 and 3.
Step 1A: systemd timer
The advantages of using Systemd timer are: * Native to modern Linux service management. * Easy to monitor with systemctl list-timers. * Survives reboots automatically. * Cleaner logging and integration with journalctl.
Many recent Certbot packages ship with a ready‑made systemd timer. To enable it, run sudo systemctl enable --now certbot.timer.
Unfortunately, in my Oracle Linux VM, I got Failed to enable unit: Unit file certbot.timer does not exist., which means my Certbot installation didn’t ship with a systemd timer unit. This is a common problem if you installed Certbot via your OS package manager on Oracle Linux, or if you used pip. In those cases, you need to set up your own systemd service + timer or use a cron job
Create a new service file
sudo nano /etc/systemd/system/certbot-renew.servicewith the following content:[Unit] Description=Certbot Renewal [Service] Type=oneshot ExecStart=/usr/bin/certbot renew --quietThis runs
certbot renewand exits.Create a new timer file
/etc/systemd/system/certbot-renew.timerwith the following content:[Unit] Description=Twice daily renewal of Let's Encrypt certificates [Timer] OnCalendar=*-*-* 00,12:00:00 Persistent=true [Install] WantedBy=timers.target- This schedules the service at midnight and noon
- If no certs are expiring, nothing happens.
- If any cert is renewed, Certbot automatically runs everything in
/etc/letsencrypt/renewal-hooks/deploy/defined in step 2 below, which reloads nginx.
Enable it with:
sudo systemctl daemon-reload sudo systemctl enable --now certbot-renew.timerCheck it’s active with
systemctl list-timers | grep certbotYou should seecertbot.timer … 12h left … certbot.servicemeaningcertbot renewwill run twice a day automatically.
Step 1B: cron job
Here are the pros and cons of using a cron job: * Works everywhere, regardless of how Certbot was installed. * Very simple to set up with crontab -e. * Harder to monitor, as you’d need to check logs manually. * If the system clock drifts or cron is disabled, it won’t run. * Less integrated with system logs.
- Add a root cron entry with
sudo crontab -e, then add this line:0 */12 * * * certbot renew --quiet. This- Runs
certbot renewevery 12 hours. --quietsuppresses output unless there’s an error.- If none are due, nothing happens.
- If any is renewed, the hook in the next section runs and reloads nginx.
- Runs
- Confirm automation with
sudo grep certbot /var/spool/cron/root
Step 2: Add a deploy hook for nginx reload
The newly renewed cert won’t be picked up until nginx is reloaded. Without that, nginx keeps serving stale cert from memory indefinitely. Fortunately, Certbot has a built‑in hook system which will run a script only if a certificate was renewed. We will leverage this hook system to reload nginx so the renewal will take effect.
- Create a new script file
/etc/letsencrypt/renewal-hooks/deploy/reload-nginx.shwith the following content
#!/bin/sh
systemctl reload nginx- Make it executable by:
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.shStep 3: Verification
Run a dry‑run again with sudo certbot renew --dry-run You should see it attempts renewal and then reloads nginx.